# -*- coding: utf-8 -*-
"""
本文件包含了策略开发用类模板，开发策略时需要继承 IStrategy 类。
策略只需根据历史数据和即时数据计算相关指标、交易信号（开多、平多、开空、平空，也可能有止损价位），
并将得到的交易信号送到相关引擎（策略引擎、测试引擎、回测引擎）的事件中，供使用者（如风险引擎、回测界面。。。）调用。
该类包含的重要属性和方法：
属性：


方法：

"""
import multiprocessing  #
from _datetime import datetime
from enum import Enum
import pandas as pd

from engine.event_engine import Event
from engine.event_type import EVENT_STRATEGY_HISTORY_SIGNALS, EVENT_STRATEGY_SIGNAL
from strategy.strategy_data import StrategySignal, STRATEGY_ORDER_BUY, STRATEGY_ORDER_COVER, STRATEGY_ORDER_SELL, \
    STRATEGY_ORDER_SHORT
from ctp.py_ctp.ctp_quote_event_type import EVENT_RtnDepthMarketData


# from indicator.df_bar import Bars, FrameBar
# from indicator.df_tick import FrameTick, Ticks


class StraStatus:
    UNKNOWN = ''
    WORKING = 'Working'
    READY = 'Ready'
    STOPPED = 'Stopped'
    TESTING = 'Testing'


class OpenDirectionType:
    BOTH = 0  # 平仓把手
    LONG = 1  # 开多
    SHORT = -1  # 开空


class IStrategy:
    inited: bool = False  # on_ticks or on_bars 后， 置为 True
    started: bool = False

    may_add = False  # 可以加仓
    may_buy = True  # 可以开多
    may_sell = False  # 可以平多
    may_short = True  # 可以开空
    may_cover = False  # 可以平空
    open_direction = 0  # 开平仓及方向：0＝双向 ，1＝多头，－1＝空头
    bs = 0

    def __init__(self, parent=None, stra_ee=None, period=0, params=None, setting=None, data_ee=None):
        self.parent = parent  # indicator.df_bar.Bars / indicator.df_tick.Ticks 实例
        self.stra_ee = stra_ee  # 策略引擎/回测引擎

        self.data_ee = data_ee  # 数据事件引擎
        self.period = period
        self.params = params
        self._id = self.__class__.__name__
        self.name = 'IStrategy'
        self.signals = {}
        self.signal = None
        self.symbol = ''
        self.pos = 0
        self.setting = setting
        self.ticks = None  # 历史ticks
        self.tick = None  # 当前 tick
        self.bars = None
        self.bar = None

        self.status = StraStatus.UNKNOWN

        if setting:
            d = self.__dict__
            for k, v in setting.items():
                if k == 'params':
                    for k1, v1 in v.items():
                        d[k1] = v1
                elif k == 'stra_params':
                    d['params'] = v
                else:
                    d[k] = v

    # def on_init(self,**kwargs):
    #     pass
    #
    def set_pos(self, pos):
        """
        设置持仓，<0:空仓，>0: 多仓，=0：持币，实盘时用，
        :param pos:
        :return:
        """
        self.pos = pos
        if pos == 0:
            self.may_buy = True
            self.may_short = True
        elif pos > 0:
            self.may_sell = True
            self.may_buy = False
            if self.may_add:
                self.may_buy = True
        else:
            self.may_buy = True
            self.may_sell = False
            if self.may_add:
                self.may_sell = True

    def to_signal(self, bs, is_ok=False):
        if bs:  # bs in [-1,1]
            if self.open_direction == 0:  # 可反手开仓
                bs = bs if is_ok else bs * 4
                if bs == 4:
                    if self.pos < 0:
                        self.cover(self.tick.AskPrice, 1)
                    if self.pos == 0 or self.may_add:
                        self.buy(self.tick.AskPrice, 1)
                elif bs == -4:
                    if self.pos > 0:
                        self.sell(self.tick.BidPrice, 1)
                    if self.pos == 0 or self.may_add:
                        self.short(self.tick.BidPrice, 1)
            elif self.open_direction == -1:
                bs = bs if is_ok else bs * 2
                if bs == 2 and self.pos < 0:
                    self.cover(self.tick.AskPrice, 1)
                elif bs == -2:
                    if self.pos == 0 or self.may_add:
                        self.short(self.tick.BidPrice, 1)
            else:
                if bs == 1:
                    if self.pos < 1 or self.may_add:
                        self.buy(self.tick.AskPrice, 1)
                elif bs == -1 and self.pos > 0:
                    self.sell(self.tick.BidPrice, 1)
            self.bs = bs

            # print(self.bs)

    def to_signals(self, bssc, is_ok=False):
        """根据可开仓方向 pos_long_short 将只有开多、开空的标志 bssc：1＝开多，－1＝开空  进行转换为
        if pos_long_short == 0:
            if bssc==1:
                bssc=4
            if bssc==-1:
                bssc=-4
        if pos_long_short == -1:
            if bssc==1:
                bssc= 2
            if bssc==-1:
                bssc=-2
         """
        # print(self.params)
        # print(len(bssc))
        if not bssc.empty:
            if self.open_direction == 0:
                bssc = bssc * 4
            elif self.open_direction == -1:
                bssc = bssc * 2
        tp = self.ticks.last_price if self.period == 0 else self.bars.c
        h = self.ticks.last_price if self.period ==0 else self.bars.h
        l = self.ticks.last_price if self.period == 0 else self.bars.l
        o = self.ticks.last_price if self.period ==0 else self.bars.o
        c = self.ticks.last_price if self.period == 0 else self.bars.c
        tp = pd.Series(tp,name='tp')
        ss = pd.DataFrame(data=tp)
        ss['high'] = h
        ss['low'] = l
        ss['open'] = o
        ss['close'] = c
        ss['bssc'] = bssc
        # self.signals ={'0': trade}
        # print(ss.query('bssc in [1,4]'))
        self.put_signals(ss)

    def on_tick(self, tick):
        print('istrategy.on_tick = tick')

    def on_ticks(self, ticks, **kwargs):
        """
        当收到历史 ticks 数据时执行，由具体策略实现, 用到的指标保存到 parent.indicators 中
        回测时可直接调用此函数
        :param ticks: DFTick
        :param kwargs: 包含策略用到的参数
        """
        print('根据历史Tick数据,计算策略信号...')
        if self.inited:
            return
        self.ticks = ticks
        pass

    def on_bar(self, bar):
        """当收到 bar 数据时执行，由具体策略实现"""
        self.tick = self.bars.tick

    def on_bars(self, bars, **kwargs):
        """
        当收到历史 bars 数据时执行，由具体策略实现，
        回测时可直接调用此函数
        :param bars: DFBar
        :param kwargs: 包含策略用到的参数
        """
        print('根据历史K线数据,计算策略信号...')
        if self.inited:
            self.bars = bars
            return
        else:
            self.inited = True
            self.bars = bars

    def buy(self, price, volume=1, stop_price=0.0, stop=False, dt=None):
        """买开=OpenLong"""
        # self.strategyEngine.openLong(self.vtSymbol, price, volume)
        if not dt:
            dt = datetime.now()
        self.send_order(STRATEGY_ORDER_BUY, price, volume, stop_price, stop, dt)
        self.set_pos(self.pos + volume)

    def sell(self, price, volume=1, stop_price=0.0, stop=False, dt=None):
        """卖平=CloseLong"""
        # self.strategyEngine.closeLong(self.vtSymbol, price, volume)
        if not dt:
            dt = datetime.now()
        self.send_order(STRATEGY_ORDER_SELL, price, volume, stop_price, stop, dt)
        self.set_pos(self.pos - volume)

    def short(self, price, volume=1, stop_price=0.0, stop=False, dt=None):
        """卖开=OpenShort"""
        # self.strategyEngine.openShort(self.vtSymbol, price, volume)
        if not dt:
            dt = datetime.now()
        self.send_order(STRATEGY_ORDER_SHORT, price, volume, stop_price, stop, dt)
        self.set_pos(self.pos - volume)

    def cover(self, price, volume=1, stop_price=0.0, stop=False, dt=None):
        """买平=CloseShort"""
        # self.strategyEngine.closeShort(self.vtSymbol, price, volume)
        if not dt:
            dt = datetime.now()
        self.send_order(STRATEGY_ORDER_COVER, price, volume, stop_price, stop, dt)
        self.set_pos(self.pos + volume)

    def send_order(self, order_type, price, volume, stop_price=0.0, stop=False, dt=None):
        # print("""发送委托 from strategy""")
        ss = StrategySignal()
        ss.datetime = dt if dt else datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
        ss.symbol = self.symbol
        ss.name = self.name
        ss.price = price
        ss.vol = volume
        ss.order_type = order_type
        ss.params = self.params
        ss.stop_price = stop_price

        now = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
        ss.datetime = now
        self.signals[now] = ss
        self.signal = ss
        self.put_signal(ss)  # , self.stra_ee)

    def put_signal(self, signal, strategy_event_engine=None):
        """由具体策略的 on_tick, on_bar 发出策略信号时调用"""
        # print('策略信号')
        if strategy_event_engine:
            self.stra_ee = strategy_event_engine
        if self.stra_ee:
            event = Event(event_type=EVENT_STRATEGY_SIGNAL + self.id, dict_={'data': signal, 'params': self.params})
            self.stra_ee.put(event)
            # print(signal.__dict__)

    def put_signals(self, signals, strategy_event_engine=None):
        """由具体策略的 on_ticks, on_bars 调用"""
        # print(self.id)
        # print(EVENT_STRATEGY_HISTORY_SIGNALS + self.id)
        if strategy_event_engine:
            self.stra_ee = strategy_event_engine
        if self.stra_ee:
            event = Event(event_type=EVENT_STRATEGY_HISTORY_SIGNALS + self.id,
                          dict_={'data': signals,'period':self.period, 'params': self.params})
            self.stra_ee.put(event)

    def stop(self):
        self.status = StraStatus.STOPPED
        self.started = False
        self.inited = False
        if self.data_ee:
            self.data_ee.unregister(EVENT_RtnDepthMarketData + self.symbol, self.on_tick)

    @property
    def id(self):
        return '_'.join([self._id, self.symbol, str(self.period)])


if __name__ == '__main__':
    is1 = IStrategy()
    is2 = IStrategy()
    is1.started = True
    print(is1.started, is2.started)
